package redislock

import (
	"errors"
	"fmt"
	"github.com/gomodule/redigo/redis"
	"strings"
	"time"
)

//Lock 基于redis的分布式锁
type Lock struct {
	id       string //当前锁的唯一ID
	lockName string //锁名称 相同名称的锁互斥
	exSecond uint64 //从Lock开始的锁过期时间
	pool     *redis.Pool
}

func NewLock(pool *redis.Pool, options ...Option) (*Lock, error) {
	if pool == nil {
		return nil, errors.New("必须指定有效的pool")
	}
	cfg := Config{
		Id:       fmt.Sprintf("%s%s", "default_", time.Now().Format("2006-01-02 15:04:05.000")),
		LockName: "lock_default",
		ExSecond: 120,
	}
	for _, v := range options {
		v(&cfg)
	}
	if strings.TrimSpace(cfg.Id) == "" {
		return nil, errors.New("必须指定有效id")
	}
	if strings.TrimSpace(cfg.LockName) == "" {
		return nil, errors.New("必须指定有效lockKey")
	}
	if cfg.ExSecond == 0 {
		return nil, errors.New("exSecond必须大于0")
	}

	return &Lock{id: cfg.Id, lockName: cfg.LockName, pool: pool, exSecond: cfg.ExSecond}, nil
}

func (r *Lock) Lock() bool {
	return r.lock()
}

func (r *Lock) LockBlock() {
	for !r.lock() {
		time.Sleep(100 * time.Millisecond)
	}
}

func (r *Lock) lock() bool {
	c := r.pool.Get()
	defer func() {
		_ = c.Close()
	}()
	ret, err := c.Do("SET", r.lockName, r.id, "NX", "EX", r.exSecond)
	if err != nil {
		return false
	}
	if ret == nil {
		return false
	}
	return true
}

func (r *Lock) Unlock() bool {
	return r.unLock()
}

func (r *Lock) UnlockBlock() {
	for !r.unLock() {
		time.Sleep(100 * time.Millisecond)
	}
}

func (r *Lock) unLock() bool {
	c := r.pool.Get()
	defer func() {
		_ = c.Close()
	}()
	//采用lua保证获取与删除的原子性
	lua := redis.NewScript(1, luaScript)
	v, err := redis.Int(lua.Do(c, r.lockName, r.id)) // 上面定义几个参数 conn 后面几个都是参数， 参数的后面就是值 按照顺序
	if err != nil {
		return false
	}
	return v != 0
}

const luaScript = `
if redis.call('get', KEYS[1])==ARGV[1] then
	return redis.call('del', KEYS[1])
else
	return 0
end
`

type Config struct {
	Id       string
	LockName string
	ExSecond uint64
}

type Option func(config *Config)

//WithId 设置锁的唯一Id
func WithId(id string) Option {
	return func(config *Config) {
		config.Id = id
	}
}

//WithLockName 设置锁名称 相同名称的锁互斥
func WithLockName(lockName string) Option {
	return func(config *Config) {
		config.LockName = lockName
	}
}

//WithExSecond 设置锁的过期时间（秒），当未成功解锁时，到时自动解锁
func WithExSecond(exSecond uint64) Option {
	return func(config *Config) {
		config.ExSecond = exSecond
	}
}
